﻿/* Waimea generate MSBuild from VS10 Solution
Copyright (C) 2011  Tim Dion

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Build.BuildEngine;

namespace PunaForrest.VS10Waimea
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidVS10WaimeaPkgString)]
    public sealed class VS10WaimeaPackage : Package
    {

        #region Member Variables and Delegates
    
        public delegate void ProcessHierarchyNodeDelegate(IVsHierarchy hierarchy, uint itemid, List<string> projectList);
        private ProcessHierarchyNodeDelegate _processNodeDelegate;
        #endregion

        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public VS10WaimeaPackage()
        {
            _processNodeDelegate = ProcessHierarchyNode;
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }


        public static void OutputCommandString(string text)
        {
            // Build the string to write on the debugger and output window.
            var outputText = new StringBuilder(text);
            outputText.Append("\n");

            // Now print the string on the output window.
            // The first step is to get a reference to IVsOutputWindow.
            var outputWindow = GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
            // If we fail to get it we can exit now.
            if (null == outputWindow)
            {
                Trace.WriteLine("Failed to get a reference to IVsOutputWindow");
                return;
            }
            // Now get the window pane for the general output.
           Guid guidGeneral = VSConstants.GUID_OutWindowGeneralPane;

           // Guid guidGeneral = VSConstants.GUID_OutWindowDebugPane; 
            IVsOutputWindowPane windowPane;
            //In VS10 we have to create output ourself.

            var result = outputWindow.CreatePane(guidGeneral, "General", 1, 0);
            if (ErrorHandler.Failed(result))
            {
                Trace.WriteLine("Failed create the Output Window General pane");
                return;
            }

             result = outputWindow.GetPane(ref guidGeneral, out windowPane);
            if (ErrorHandler.Failed(result))
            {
                Trace.WriteLine("Failed to get a reference to the Output Window General pane");
                return;
            }
            result = windowPane.OutputString(outputText.ToString());
            if (ErrorHandler.Failed(result))
            {
                Trace.WriteLine("Failed to write on the output window");
            }
        }
        /////////////////////////////////////////////////////////////////////////////
        // Overriden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuCommandID = new CommandID(GuidList.guidVS10WaimeaCmdSet, (int)PkgCmdIDList.cmdidMSBuild);
                MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
                mcs.AddCommand( menuItem );
            }
        }
        #endregion

        /// <summary>
        /// This function is the callback used to execute a command when the a menu item is clicked.
        /// See the Initialize method to see how the menu item is associated to this function using
        /// the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void MenuItemCallback(object sender, EventArgs e)
        {
   
            var projectList = new List<string>();
            //Get the solution service so we can traverse each project hierarchy contained within.
            var solution = (IVsSolution)GetService(typeof(SVsSolution));
            if (null != solution)
            {
                var solutionHierarchy = solution as IVsHierarchy;
                if (null != solutionHierarchy)
                {
                    OutputCommandString("\n\nGenerating MSBuild file \n");
                    EnumHierarchyItems(solutionHierarchy, VSConstants.VSITEMID_ROOT, 0, false, _processNodeDelegate, projectList);
                }

                string solutionDirectory;
                string solutionFile;
                string userOptsFile;
                solution.GetSolutionInfo(out solutionDirectory, out solutionFile, out userOptsFile);

                GenerateMsBuild(projectList,  solutionDirectory,  solutionFile,  userOptsFile);
            }
        }
        /// <summary>
        /// Enumerates over the hierarchy items for the given hierarchy traversing into nested hierarchies.
        /// </summary>
        /// <param name="hierarchy">hierarchy to enmerate over.</param>
        /// <param name="itemid">item id of the hierarchy</param>
        /// <param name="recursionLevel">Depth of recursion. e.g. if recursion started with the Solution
        /// node, then : Level 0 -- Solution node, Level 1 -- children of Solution, etc.</param>
        /// <param name="hierIsSolution">true if hierarchy is Solution Node. This is needed to special
        /// case the children of the solution to work around a bug with VSHPROPID_FirstChild and 
        /// VSHPROPID_NextSibling implementation of the Solution.</param>
        /// <param name="visibleNodesOnly">true if only nodes visible in the Solution Explorer should
        /// be traversed. false if all project items should be traversed.</param>
        /// <param name="processNodeDelegateFunc">pointer to function that should be processed on each
        /// node as it is visited in the depth first enumeration.</param>
        private void EnumHierarchyItems(IVsHierarchy hierarchy, uint itemid, int recursionLevel, bool hierIsSolution,
                                         ProcessHierarchyNodeDelegate processNodeDelegateFunc,
                                        List<string> projectList)
        {

            IntPtr nestedHierarchyObj;
            uint nestedItemId;
            Guid hierGuid = typeof(IVsHierarchy).GUID;

            // Check first if this node has a nested hierarchy. If so, then there really are two 
            // identities for this node: 1. hierarchy/itemid 2. nestedHierarchy/nestedItemId.
            // We will recurse and call EnumHierarchyItems which will display this node using
            // the inner nestedHierarchy/nestedItemId identity.
            int result = hierarchy.GetNestedHierarchy(itemid, ref hierGuid, out nestedHierarchyObj, out nestedItemId);
            if (VSConstants.S_OK == result && IntPtr.Zero != nestedHierarchyObj)
            {
                var nestedHierarchy = Marshal.GetObjectForIUnknown(nestedHierarchyObj) as IVsHierarchy;
                Marshal.Release(nestedHierarchyObj);
                // we are responsible to release the refcount on the out IntPtr parameter
                if (nestedHierarchy != null)
                {
                    // Display name and type of the node in the Output Window
                    EnumHierarchyItems(nestedHierarchy, nestedItemId, recursionLevel, false,
                                       processNodeDelegateFunc, projectList);
                }
            }
            else
            {
                object pVar;

                // Display name and type of the node in the Output Window
                processNodeDelegateFunc(hierarchy, itemid, projectList);

                //Get the first child node of the current hierarchy being walked
                // NOTE: to work around a bug with the Solution implementation of VSHPROPID_FirstChild,
                // we keep track of the recursion level. If we are asking for the first child under
                // the Solution, we use VSHPROPID_FirstVisibleChild instead of _FirstChild. 
                // In VS 2005 and earlier, the Solution improperly enumerates all nested projects
                // in the Solution (at any depth) as if they are immediate children of the Solution.
                // Its implementation _FirstVisibleChild is correct however, and given that there is
                // not a feature to hide a SolutionFolder or a Project, thus _FirstVisibleChild is 
                // expected to return the identical results as _FirstChild.
                result = hierarchy.GetProperty(itemid,
                                           (((hierIsSolution && recursionLevel == 1)
                                                 ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild
                                                 : (int)__VSHPROPID.VSHPROPID_FirstChild)),
                                           out pVar);
                ErrorHandler.ThrowOnFailure(result);
                if (VSConstants.S_OK == result)
                {
                    //We are using Depth first search so at each level we recurse to check if the node has any children
                    // and then look for siblings.
                    uint childId = GetItemId(pVar);
                    while (childId != VSConstants.VSITEMID_NIL)
                    {
                        EnumHierarchyItems(hierarchy, childId, recursionLevel, false, processNodeDelegateFunc, projectList);
                        // NOTE: to work around a bug with the Solution implementation of VSHPROPID_NextSibling,
                        // we keep track of the recursion level. If we are asking for the next sibling under
                        // the Solution, we use VSHPROPID_NextVisibleSibling instead of _NextSibling. 
                        // In VS 2005 and earlier, the Solution improperly enumerates all nested projects
                        // in the Solution (at any depth) as if they are immediate children of the Solution.
                        // Its implementation   _NextVisibleSibling is correct however, and given that there is
                        // not a feature to hide a SolutionFolder or a Project, thus _NextVisibleSibling is 
                        // expected to return the identical results as _NextSibling.
                        result = hierarchy.GetProperty(childId,
                                                   (((hierIsSolution && recursionLevel == 1))
                                                        ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling
                                                        : (int)__VSHPROPID.VSHPROPID_NextSibling),
                                                   out pVar);
                        if (VSConstants.S_OK == result)
                        {
                            childId = GetItemId(pVar);
                        }
                        else
                        {
                            ErrorHandler.ThrowOnFailure(result);
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the item id.
        /// </summary>
        /// <param name="pvar">VARIANT holding an itemid.</param>
        /// <returns>Item Id of the concerned node</returns>
        private uint GetItemId(object pvar)
        {
            if (pvar == null) return VSConstants.VSITEMID_NIL;
            if (pvar is int) return (uint)(int)pvar;
            if (pvar is uint) return (uint)pvar;
            if (pvar is short) return (uint)(short)pvar;
            if (pvar is ushort) return (ushort)pvar;
            if (pvar is long) return (uint)(long)pvar;
            return VSConstants.VSITEMID_NIL;
        }
        /// <summary>
        /// This function diplays the name of the Hierarchy node. This function is passed to the 
        /// Hierarchy enumeration routines to process the current node.
        /// </summary>
        /// <param name="hierarchy">Hierarchy of the current node</param>
        /// <param name="itemid">Itemid of the current node</param>
        /// 
        private void ProcessHierarchyNode(IVsHierarchy hierarchy, uint itemid, List<string> projectList)
        {

            int result;

            //Get the name of the root node in question here and dump its value
            object pVarName;
            object pVarType;
            object pVarProjectDir;

            result = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out pVarName);
            if (result != VSConstants.S_OK)
            {
                Trace.WriteLine(String.Format("Walking hierarchy node: {0}", (string)pVarName));
                return;
            }

            result = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_TypeName, out pVarType);
            if (result != VSConstants.S_OK)
            {
                Trace.WriteLine(String.Format("Walking hierarchy node: {0}", (string)pVarType));
                return;
            }

            result = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_ProjectDir, out pVarProjectDir);
            if (result != VSConstants.S_OK)
            {
                Trace.WriteLine(String.Format("Walking hierarchy node: {0}", (string)pVarProjectDir));
                return; 
            }

            //Todo add C++, VB, WIX, and others. Or get project file name
            if ((string)pVarType == "Microsoft Visual C# 2010")
            {
                var projectFile = (string)pVarProjectDir + "\\" + (string)pVarName + ".csproj ";

                // OutputCommandString(projectFile);
                projectList.Add(projectFile);
            }
        }

        private void GenerateMsBuild(List<string> fileList, string solutionDirectory, string solutionFile, string userOptsFile)
        {


            OutputCommandString("Solution:" + Path.Combine(solutionDirectory, solutionFile));
            foreach (string projectFileName in fileList)
            {
                var project = new Project();        //Todo use Evaluation.Project
                try
                {
                    project.Load(projectFileName);
                    OutputCommandString("   Project:" + project.FullFileName);
                    ProjectNode.AddNode(projectFileName);
                }
                catch (InvalidProjectFileException exception)
                {
                    Trace.WriteLine("GenerateMsBuild:" + exception.Message);
                }

            }

            ProjectNode.BuildAllGraphs();
            ProjectNode.WalkAll();
            ProjectNode.OutputAll(solutionDirectory, solutionFile, userOptsFile);

        }

    }
}
